using System;
using System.Linq;
using System.Reflection;
using MetaObjects.Contracts;

namespace MetaObjects.Core
{
	public class AttributeProvider<TEntity> : IMetaEntityProvider<Type>, IMetaFieldProvider<PropertyInfo>
	{
		public Func<PropertyInfo, IMetaField> FieldFactory { get; set; }
		Func<object, IMetaField> IMetaFieldProvider.FieldFactory
		{
			get { return f => FieldFactory((PropertyInfo)f); }
		}

		public Func<Type, IMetaEntity> EntityFactory { get; set; }
		Func<object, IMetaEntity> IMetaEntityProvider.EntityFactory
		{
			get { return e => EntityFactory((Type)e); }
		}

		public AttributeProvider()
		{
			EntityFactory = GetMetaEntity;
			FieldFactory = GetMetaField;
		}

		public IMetaEntity<TEntity> GetMetaEntity(Func<TEntity> factory)
		{
			var entity = typeof(TEntity);
			var fields = entity.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Select(f => GetMetaField(f));
			MetaEntity<TEntity> metaEntity = new MetaEntity<TEntity>(entity.Name, factory, fields);
			return metaEntity;
		}

		public IMetaEntity GetMetaEntity(Type type)
		{
			var metaType = type.GetAttribute<MetaTypeAttribute>();

			var fields = metaType.Type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Select(FieldFactory);
			string name = metaType.Type.Name;
			Func<object> factory = metaType.Factrory ?? (() => Activator.CreateInstance(metaType.Type));
			MetaEntity<object> metaEntity = new MetaEntity<object>(name, factory, fields);
			return metaEntity;
		}

		public IMetaField GetMetaField(PropertyInfo property)
		{
			var metaType = property.DeclaringType.GetAttribute<MetaTypeAttribute>();
			var metaProperty = property.GetAttribute<MetaPropertyAttribute>();

			string internalName = metaProperty.InternalName ?? property.Name;

			Func<object, object> getField = metaProperty.GetField ?? (entity => metaType.Type.GetProperty(internalName).GetValue(entity, new object[0]));
			Action<object, object> setField = metaProperty.SetField ?? ((entity, value) => metaType.Type.GetProperty(internalName).SetValue(entity, value, new object[0]));

			MetaField metaField = new MetaField(internalName)
          	{
          		DisplayName = internalName,
          		GetField = getField,
          		SetField = setField,
          	};
			return metaField;
		}
	}
}